import {
  ECurveParamsType,
  ArcCurveParam,
  BulgeCurveParam,
  ICircle,
  IntermediateCurveParam,
  LineCurveParam,
  PairCurveParams,
  PointObj2,
  StartEndPoint,
} from '@/types';
import {
  getArcByBulge,
  getBulgeByStartEndAndCenter,
  getIntersectionOfCurves,
  getPerpVector,
  object2Vector2,
  pair,
} from '@/utils';
import { merge } from 'lodash-es';
import { ArcCurve, LineCurve, Vector2 } from 'three';

export function createLineCurveParam(
  params: Omit<LineCurveParam, 'type'>,
): LineCurveParam {
  return merge({ type: ECurveParamsType.Line as const }, params);
}
export function createArcCurveParam(
  params: Omit<ArcCurveParam, 'type'>,
): ArcCurveParam {
  return merge({ type: ECurveParamsType.Arc as const }, params);
}
export function bulgeCurveParam2IntermediaCurveParam(
  curveParam: BulgeCurveParam,
): IntermediateCurveParam {
  if (curveParam.bulge === 0) {
    return createLineCurveParam(curveParam);
  } else {
    const { startPoint, endPoint, bulge } = curveParam;
    const { center, radius, isCCW } = getArcByBulge(
      startPoint,
      endPoint,
      bulge,
    );
    return createArcCurveParam({
      startPoint,
      endPoint,
      circle: { center, radius },
      isCCW,
    });
  }
}
export function intermediateCurveParam2BulgeCurveParam(
  intermediateCurveParam: IntermediateCurveParam,
): BulgeCurveParam {
  const { startPoint, endPoint } = intermediateCurveParam;
  if (intermediateCurveParam.type === ECurveParamsType.Line) {
    return { startPoint, endPoint, bulge: 0 };
  } else {
    const { circle, isCCW } = intermediateCurveParam;
    const { center } = circle;
    const bulge = getBulgeByStartEndAndCenter({
      startPoint,
      endPoint,
      center,
      isCCW,
    });
    return { startPoint, endPoint, bulge };
  }
}
export function getFillCurveParamByCircle(
  [curveParam1, curveParam2]: PairCurveParams,
  circle: ICircle,
  isCCW: boolean,
): ArcCurveParam {
  return createArcCurveParam({
    startPoint: curveParam1.endPoint,
    endPoint: curveParam2.startPoint,
    circle,
    isCCW,
  });
}

export function getContinuousCurveParams(
  intermediateCurveParams: IntermediateCurveParam[],
  width: number,
  fillCurveParam: (
    [curveParam1, curveParam2]: PairCurveParams<IntermediateCurveParam>,
    index: number,
  ) => IntermediateCurveParam,
): IntermediateCurveParam[] {
  if (intermediateCurveParams.length < 2) {
    return intermediateCurveParams;
  }

  let preIntersectionPoint: PointObj2 | null = null;
  const newCurveParams: IntermediateCurveParam[] = [];
  pair(intermediateCurveParams, ([curveParam1, curveParam2], index) => {
    let intersectionPoint = getIntersectionOfCurves([curveParam1, curveParam2]);
    const startPoint = preIntersectionPoint || curveParam1.startPoint;
    if (
      !intersectionPoint &&
      object2Vector2(curveParam1.endPoint).distanceTo(
        object2Vector2(curveParam2.startPoint),
      ) < 1e-3
    ) {
      intersectionPoint = curveParam1.endPoint;
    }
    if (intersectionPoint) {
      preIntersectionPoint = intersectionPoint;
      const endPoint = intersectionPoint;
      newCurveParams.push(
        getDeriveCurveParam(curveParam1, { startPoint, endPoint }),
      );
    } else {
      const center = curveParam1.endPoint;
      const radius = width;
      const filledCurveParam = fillCurveParam(
        [curveParam1, curveParam2],
        index,
      );
      newCurveParams.push(curveParam1, filledCurveParam);
    }
  });
  {
    const lastCurveParam = intermediateCurveParams.at(-1)!;
    const lastNewCurveParam = newCurveParams.at(-1)!;
    newCurveParams.push(
      getDeriveCurveParam(lastCurveParam, {
        startPoint: lastNewCurveParam.endPoint,
        endPoint: lastCurveParam.endPoint,
      }),
    );
  }

  return newCurveParams;
}
function getDeriveCurveParam(
  curveParam: IntermediateCurveParam,
  { startPoint, endPoint }: StartEndPoint,
) {
  if (curveParam.type === ECurveParamsType.Line) {
    return createLineCurveParam({ startPoint, endPoint });
  } else {
    const { circle, isCCW } = curveParam;
    return createArcCurveParam({ startPoint, endPoint, circle, isCCW });
  }
}

export function reverseCurveParams(curveParams: IntermediateCurveParam[]) {
  return curveParams
    .map((curveParam) => {
      const newCurveParam = {
        ...curveParam,
        startPoint: curveParam.endPoint,
        endPoint: curveParam.startPoint,
      };
      if (newCurveParam.type === ECurveParamsType.Arc) {
        newCurveParam.isCCW = !newCurveParam.isCCW;
      }
      return newCurveParam;
    })
    .reverse();
}
export function getOffsetCurveParams<T extends BulgeCurveParam>(
  curveParam: T,
  width: number,
) {
  let outerCurveParam: T;
  let innerCurveParam: T;

  const { startPoint, endPoint } = curveParam;
  const curve = getCurveFromBulge(curveParam);
  const [startOffsetVector2, endOffsetVector2] = [
    curve.getTangent(0),
    curve.getTangent(1),
  ].map((vertor2) => getPerpVector(vertor2).multiplyScalar(width / 2));
  const startPointVector2 = new Vector2(startPoint.x, startPoint.y);
  const endPointVector2 = new Vector2(endPoint.x, endPoint.y);
  outerCurveParam = {
    ...curveParam,
    startPoint: startPointVector2.clone().add(startOffsetVector2),
    endPoint: endPointVector2.clone().add(endOffsetVector2),
  };
  innerCurveParam = {
    ...curveParam,
    startPoint: startPointVector2.clone().sub(startOffsetVector2),
    endPoint: endPointVector2.clone().sub(endOffsetVector2),
  };

  return { outerCurveParam, innerCurveParam };
}

export function getCurveFromBulge(curveParam: BulgeCurveParam) {
  if (curveParam.bulge === 0) {
    return new LineCurve(
      object2Vector2(curveParam.startPoint),
      object2Vector2(curveParam.endPoint),
    );
  } else {
    const { startPoint, endPoint, bulge } = curveParam;
    const arc = getArcByBulge(startPoint, endPoint, bulge);
    const { center, radius, startAngle, endAngle, isCCW } = arc;
    return new ArcCurve(
      center.x,
      center.y,
      radius,
      startAngle,
      endAngle,
      !isCCW,
    );
  }
}
